Skip to content

feat(attio): add attribute tools + fix API alignment gaps#5445

Merged
waleedlatif1 merged 8 commits into
stagingfrom
attio-integration-audit
Jul 7, 2026
Merged

feat(attio): add attribute tools + fix API alignment gaps#5445
waleedlatif1 merged 8 commits into
stagingfrom
attio-integration-audit

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

Summary

  • Add 4 new tools for Attio's attribute schema management: attio_list_attributes, get_attribute, create_attribute, update_attribute
  • Fix missing completed_at output on task tools
  • Fix note tags output to match Attio's real response shape (workspace-member vs record tag variants)
  • Fix attribute outputs missing is_default_value_enabled, default_value, relationship fields
  • Fix create_comment sending both entry and thread_id in the same request (Attio requires exactly one)
  • Wire attribute pagination and task sort into the block UI

Type of Change

  • New feature
  • Bug fix

Testing

Tested manually. Ran full validate-integration audit against Attio's live REST API docs across 5 independent passes (records/objects/attributes, lists/list-entries, tasks/notes/comments/threads, members/webhooks/block-wiring, backwards-compat diff). Confirmed change is purely additive — no existing tool ID, param, or output field was removed or renamed.

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

- Add 4 new tools: attio_list_attributes, get_attribute, create_attribute, update_attribute
- Add missing completed_at field to task tools
- Fix note tags output to match Attio's actual response shape (workspace-member vs record tags)
- Fix attribute outputs missing is_default_value_enabled, default_value, relationship
- Fix create_comment sending both entry and thread_id (Attio requires exactly one)
- Wire attribute pagination + task sort into the block
@vercel

vercel Bot commented Jul 6, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Jul 6, 2026 11:55pm

Request Review

@cursor

cursor Bot commented Jul 6, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Touches many Attio tools and block param wiring (including comment and schema create/update); behavior changes are mostly additive with backward-compat fallbacks, but create-attribute and stricter JSON errors can surface new failures in live workflows.

Overview
Adds four attribute schema operations (list, get, create, update) on Attio objects and lists, with matching block UI, registry entries, and shared ATTRIBUTE_OUTPUT_PROPERTIES (including default value and relationship fields).

Comment creation now picks exactly one target via a Comment On control (list entry, record, or thread reply) and builds a mutually exclusive API body; legacy blocks without commentTarget still default to entry or thread behavior.

Partial-update UX splits create vs update dropdowns for task completion, list workspace access, and attribute flags, using Leave unchanged so PATCH-style updates do not overwrite fields unintentionally. The block params mapper is operation-scoped so stale form values do not leak across operations.

Output and API fixes: task tools expose completedAt; note tags are normalized with mapNoteTags (workspace-member vs record); list_tasks gains completed_at sort in the UI; invalid JSON for record values, filters, and sorts throws instead of sending empty payloads.

Reviewed by Cursor Bugbot for commit 43f1662. Configure here.

Comment thread apps/sim/blocks/blocks/attio.ts
Comment thread apps/sim/tools/attio/update_attribute.ts
@greptile-apps

greptile-apps Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds four new attribute schema management tools (attio_list_attributes, attio_get_attribute, attio_create_attribute, attio_update_attribute) and fixes several API alignment gaps across the existing Attio integration.

  • New attribute tools: Full CRUD for object/list attribute schemas, with correct handling of config: {} (required by Attio's API even when empty) and JSON parse errors thrown explicitly.
  • create_comment overhaul: Enforces mutual exclusivity of the three comment targets (entry, record, thread reply) with backward-compatible fallback for old saved blocks; the body now correctly sends exactly one of entry, record, or thread_id.
  • Block wiring cleanup: Each block param is now scoped to only the operations that expose it, preventing stale values from a previous operation leaking into unrelated requests; shared fields like taskIsCompleted and listWorkspaceAccess are split into create/update variants with an unchanged sentinel, and backward compat fallbacks preserve behavior for blocks saved before the split.

Confidence Score: 4/5

Safe to merge for the new attribute tools and comment/block fixes; the task tools carry a pre-existing silent-substitution issue (malformed JSON silently resets linked_records/assignees to []) that was flagged in a prior review cycle but not addressed in this PR.

The new attribute tools, comment refactor, and block wiring cleanup are all correct and well-guarded. The unresolved concern is in create_task.ts and update_task.ts: a malformed linkedRecords or assignees JSON string silently substitutes [], so a PATCH can wipe existing task associations without surfacing an error. Every other tool touched in this PR (create_record, update_record, assert_record, create_attribute, update_attribute) now throws on bad JSON, making the task tools the remaining outlier.

apps/sim/tools/attio/create_task.ts and apps/sim/tools/attio/update_task.ts — the silent JSON catch still substitutes [] instead of throwing, inconsistent with how the rest of the Attio tools were fixed in this PR.

Important Files Changed

Filename Overview
apps/sim/blocks/blocks/attio.ts Large block wiring refactor: scopes each param to its correct operation, adds 4 new attribute operations, splits shared fields (taskIsCompleted, listWorkspaceAccess) into create/update variants with "unchanged" sentinel for updates, and adds backward-compat fallback for blocks saved before the split. Logic is sound.
apps/sim/tools/attio/create_comment.ts Correctly enforces mutual-exclusivity of entry/record/thread targets and throws when none of the required fields are present. Tool description is stale (still says "list entry" only).
apps/sim/tools/attio/create_attribute.ts New tool; correctly sends config:{} as Attio's API requires that key even when empty, and throws on invalid JSON rather than silently substituting.
apps/sim/tools/attio/update_attribute.ts New tool; config JSON parse failure now throws instead of silently overwriting. PATCH body only includes fields that are explicitly set. Clean.
apps/sim/tools/attio/create_task.ts Silent JSON substitution (linkedRecords/assignees silently fall back to []) is unchanged. Other record/note/attribute tools in this PR were fixed to throw; this inconsistency was flagged in a prior review but remains unaddressed.
apps/sim/tools/attio/update_task.ts Same silent-substitution pattern as create_task.ts: a malformed linkedRecords/assignees string causes a PATCH with [] rather than an error, silently wiping existing associations. Flagged in prior review; not yet fixed.
apps/sim/tools/attio/list_attributes.ts New tool; correctly builds query string for limit/offset/showArchived and maps all API fields including is_default_value_enabled, default_value, and relationship.
apps/sim/tools/attio/list_tasks.ts Adds sort param including new completed_at variants; sort description in this file is already correct. completedAt is now mapped from the API response.
apps/sim/tools/attio/types.ts Adds new attribute param/response interfaces and NoteTagOutput fix; AttioResponse union updated with all four new attribute tool types. No issues found.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[create_comment called] --> B{commentTarget?}
    B -->|undefined| C{commentThreadId set?}
    C -->|yes| D[target = 'thread']
    C -->|no| E[target = 'entry']
    B -->|'entry'| E
    B -->|'record'| F[target = 'record']
    B -->|'thread'| D

    E --> G{list + entryId set?}
    G -->|yes| H[body.entry = list + entry_id]
    G -->|no| I[throw: must provide entry fields]

    F --> J{recordObject + recordId set?}
    J -->|yes| K[body.record = object + record_id]
    J -->|no| I

    D --> L{threadId set?}
    L -->|yes| M[body.thread_id = threadId]
    L -->|no| I

    H --> N[POST /v2/comments]
    K --> N
    M --> N
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[create_comment called] --> B{commentTarget?}
    B -->|undefined| C{commentThreadId set?}
    C -->|yes| D[target = 'thread']
    C -->|no| E[target = 'entry']
    B -->|'entry'| E
    B -->|'record'| F[target = 'record']
    B -->|'thread'| D

    E --> G{list + entryId set?}
    G -->|yes| H[body.entry = list + entry_id]
    G -->|no| I[throw: must provide entry fields]

    F --> J{recordObject + recordId set?}
    J -->|yes| K[body.record = object + record_id]
    J -->|no| I

    D --> L{threadId set?}
    L -->|yes| M[body.thread_id = threadId]
    L -->|no| I

    H --> N[POST /v2/comments]
    K --> N
    M --> N
Loading

Reviews (13): Last reviewed commit: "fix(attio): preserve legacy taskIsComple..." | Re-trigger Greptile

Comment thread apps/sim/tools/attio/create_attribute.ts Outdated
- Throw on invalid config JSON instead of silently overwriting with {}
  (create_attribute, update_attribute)
- create_attribute: omit config field entirely when not provided instead
  of always sending {}
- Add "Leave unchanged" option to Required/Unique dropdowns so
  update_attribute no longer clears existing constraints on unrelated
  field updates
- Fix stale sort param description on list_tasks (was missing
  completed_at:asc/desc variants)
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

Addressed round-1 feedback in a87e0fa:

  • update_attribute/create_attribute: invalid config JSON now throws instead of silently overwriting with {}
  • create_attribute: config omitted entirely when not provided (was always sending {})
  • Required/Unique dropdowns for create/update attribute now default to "Leave unchanged" instead of "No", so update_attribute no longer clears existing required/unique constraints on unrelated edits (Cursor's High severity finding)
  • list_tasks sort param description now documents the completed_at:asc/desc variants (Greptile's outside-diff P2)

@greptile review
@cursor review

…exclusion

- create_comment: support all 3 of Attio's mutually-exclusive comment
  targets (thread_id, record, entry), not just thread_id/entry
- Fix update_task silently clearing is_completed on unrelated field
  updates (taskIsCompletedUpdate now defaults to "leave unchanged")
- Fix update_list silently resetting workspace_access to full-access on
  unrelated field updates (listWorkspaceAccessUpdate, same pattern)
- create_attribute: restore config:{} as always-required per Attio's
  schema (previously omitted it entirely, which is invalid on create)
- create_record/update_record/assert_record/list_records: throw on
  invalid values/filter/sorts JSON instead of silently substituting {}
  (was a silent data-loss risk on malformed input)
- get_task: drop stray Content-Type header on a GET request
- types.ts: remove two dead unreferenced interfaces, fix
  workspaceMemberAccess type (was string, is actually an array)
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

Pushed a second round of fixes (55c63b7) from an independent parallel re-validation pass — fixed 2 more silent-clobber bugs on update operations (task completion status, list workspace access being reset to defaults on unrelated edits), completed the create_comment 3-way mutual exclusion (thread_id/record/entry per Attio's actual schema), fixed silent JSON-parse-to-empty-object fallbacks on record create/update/assert (was a data-loss risk), and restored the required config:{} default on attribute creation per Attio's OpenAPI spec.

@greptile review
@cursor review

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile review

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/sim/blocks/blocks/attio.ts Outdated
Comment thread apps/sim/blocks/blocks/attio.ts Outdated
Params like taskIsCompleted/listWorkspaceAccess/attributeIsMultiselect/
attributeIsArchived are only meant for one specific operation, but their
mapping wasn't checking params.operation. A stale value persisted in
block state from a prior operation selection (e.g. switching the
dropdown from create_task to update_task) could leak through and
override the "leave unchanged" default on the other operation.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

Fixed the 2 new High-severity Cursor findings from the 55c63b7 review (stale taskIsCompleted/listWorkspaceAccess param leakage across operation switches) in 4245f3a — see inline replies. Also audited every other operation-scoped default in the block for the same class of issue and gated attributeIsMultiselect/attributeIsArchived defensively.

@greptile review
@cursor review

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/sim/blocks/blocks/attio.ts
Comment thread apps/sim/tools/attio/create_comment.ts
…-state

- Add "Leave unchanged" default to attributeIsArchived dropdown, same
  pattern as isRequired/isUnique, so a stale archived flag from an
  earlier edit can't unarchive an attribute on an unrelated update
- create_comment: replace field-presence inference (which let stale
  record fields hijack a list-entry comment or vice versa) with an
  explicit commentTarget selector (List Entry / Record / Reply to
  Thread). Only the fields for the selected target are ever forwarded
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

Fixed both High-severity findings from the last Cursor pass in 7b23b14 — attributeIsArchived now has a Leave-unchanged default, and create_comment now uses an explicit commentTarget selector instead of field-presence inference. See inline replies.

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/sim/blocks/blocks/attio.ts Outdated
Comment thread apps/sim/blocks/blocks/attio.ts
Comment thread apps/sim/blocks/blocks/attio.ts
…ack-compat

- taskFilterCompleted (list_tasks filter) was mapped to isCompleted for
  every operation; gate it to list_tasks so it can't override the
  isCompleted value on an unrelated update_task call
- Generic threadId (get_thread) was unconditionally forwarded and
  create_comment prioritizes thread_id first, so a leftover threadId
  from configuring get_thread could silently hijack a comment meant for
  a list entry or record; gate it to get_thread
- Default commentTarget to the pre-existing behavior (entry, or thread
  if commentThreadId is set) when absent, so blocks saved before this
  field existed keep working instead of throwing
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

Fixed all 3 findings from the last Cursor pass in c0fe19b — taskFilterCompleted and the generic threadId field are now gated to their originating operations (list_tasks / get_thread), and commentTarget falls back to the pre-existing behavior when absent so previously-saved blocks don't break. See inline replies.

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/sim/blocks/blocks/attio.ts
Comment thread apps/sim/blocks/blocks/attio.ts
…e-value class of bugs

The params() function reuses cleanParams keys (title, content, apiSlug,
filter, sorts, recordId, entryId, list, object, threadId, ...) across
several unrelated operation families. Every mapping was unconditional on
presence alone, so a stale value left in block state from a previously
selected operation could silently leak into an unrelated request and
overwrite the intended value (last-write-wins on a shared key).

Rewrote the whole function to gate every line by params.operation
against the exact operation set its subBlock's condition exposes it
under, closing this entire bug class in one pass instead of patching
individual instances as they were found in review.

Also split attributeIsRequired/attributeIsUnique into create-only
(default false) and update-only (tri-state, default "leave unchanged")
variants — the shared field let an explicit Yes/No choice from
create_attribute carry over and silently change constraints on an
unrelated update_attribute call.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

Given the recurring pattern of stale-value findings across multiple rounds (task, list, attribute, comment fields), I rewrote the entire tools.config.params function in ba5132c to gate every single cleanParams mapping by params.operation against the exact operation set from that field's own subBlock condition — not just the two fields flagged this round. This closes the whole class of cross-operation param leakage in one pass rather than continuing to patch individual instances.

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/sim/blocks/blocks/attio.ts Outdated
…date

taskIsCompleted and listWorkspaceAccess pre-date this PR as fields
shared between create and update operations (confirmed present on
origin/staging). Splitting them into create/update variants earlier
this session meant existing saved blocks with a value stored under the
legacy field name would silently stop applying it on update_task /
update_list once the new -Update field (always undefined for old
blocks) took over. Fall back to the legacy field when the new field is
untouched, so old saved workflows keep behaving exactly as before.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

Fixed the legacy-field back-compat regression in 43f1662 — taskIsCompleted/listWorkspaceAccess predate this PR and needed a fallback path for pre-existing saved blocks. See inline reply.

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 43f1662. Configure here.

@waleedlatif1 waleedlatif1 merged commit 586b97f into staging Jul 7, 2026
13 checks passed
@waleedlatif1 waleedlatif1 deleted the attio-integration-audit branch July 7, 2026 00:01
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

Fixed in d2f5230 — create_task/update_task now throw on invalid linkedRecords/assignees JSON instead of silently substituting []. While auditing for this exact pattern, I also proactively found and fixed 6 more instances of the same silent-parse-to-empty-value bug that hadn't been flagged yet: create_list_entry/update_list_entry (entryValues), create_webhook/update_webhook (subscriptions), and query_list_entries (filter, sorts). All now throw a clear error instead of silently dropping user data.

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 43f1662. Configure here.

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

Fixed the last open item — create_comment's tool description now reflects all 3 supported targets (list entry / record / thread reply). Combined with the 7 JSON-parse fixes from d2f5230, this should close everything flagged so far.

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 43f1662. Configure here.

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

Noting for the record: this exact finding was already fixed in d2f5230 (create_task.ts/update_task.ts now throw Invalid JSON provided for linked records/assignees instead of substituting []) — verified present in the current HEAD (89c7da7). Greptile's footer shows "Last reviewed commit: 43f1662", which predates that fix, so this looks like review lag rather than a live issue. Re-triggering once more.

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant